package pl.edu.agh.jemo.evolution.operator.mutation.impl;

import javax.swing.JPanel;

import pl.edu.agh.jemo.evolution.common.JemoRandom;
import pl.edu.agh.jemo.evolution.genotype.Genotype;
import pl.edu.agh.jemo.evolution.operator.common.DomainControl;
import pl.edu.agh.jemo.evolution.operator.mutation.Mutation;
import pl.edu.agh.jemo.evolution.specimen.Domination;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.panels.mutation.ClassicMutationConfigurationPanel;

/**
 * This mutation implementation modifies randomly given specimen's genotype.
 * Genotype is represented as a bit sequence, and some bits of this sequence are modified.
 * This class performs also domain checks and assures proper interpretation of mutationChance related parameters.
 * 
 * @author Marcin Zbijowski
 *
 */
public class ClassicMutation implements Mutation {

	/**
	 * Specifies probability of mutation occurence. Represents also single specimen bit change chance.
	 */
	protected double mutationChance = 0.15;
	/**
	 * Informs if it is required for new specimen, to be stronger than parent specimen.
	 */
	protected boolean improveSpecimen = true;
	/**
	 * Stores currently chosen domain control policy.
	 */
	protected DomainControl domainControl = DomainControl.MOVE_TO_BORDER;
	
	/**
	 * Modifies given specimen genotype, based on stored parameters values.
	 * Tests mutationChance parameter, if test fails, no more actions woudl be taken.
	 * Creates new Specimen, that differs from the given, lies in domain, or is better than it's parent, if needed.
	 * 
	 * @param specimen Reference to a parent specimen, that needs to be changed by operator.
	 */
	@Override
	public void performMutation(Specimen specimen) {
		// If this fails no mutation should happen at all
		if (JemoRandom.getRandom().nextDouble() > mutationChance) return;
		
		Specimen newSpecimen = specimen.clone();
		// Mutation condition succeed, so new specimen must differ
		while (newSpecimen.equals(specimen)) {
			mutate(newSpecimen);
			switch (domainControl) {
			case GENERATE_NEXT:
				while (!newSpecimen.isInDomain()) {
					mutate(newSpecimen);
				}
				break;
			case MOVE_TO_BORDER:
				if (!newSpecimen.isInDomain()) {
					newSpecimen.moveToDomain();
				}
			case IGNORE:
			default:
			}
			if (improveSpecimen) {
				newSpecimen.calculatePhenotype();
				specimen.calculatePhenotype();
				if (newSpecimen.dominationRelationFirstCriteria(specimen) == Domination.IS_DOMINATED)
					return;
			}
		}
		specimen.setGenotype(newSpecimen.getGenotype());
	}

	/**
	 * Performs mutation of given specimen.
	 * Modifies every gene, using generated bits array.
	 * 
	 * @param specimen Reference to specimen object, that needs to be modified.
	 */
	protected void mutate(Specimen specimen) {
		Genotype[] genotype = specimen.getGenotype();
		for (int i = 0; i < genotype.length; i++) {
			genotype[i].fromDouble(Double.longBitsToDouble(Double
					.doubleToLongBits(genotype[i].asDouble()) ^ encodeMutation()));
		}
	}

	/**
	 * Generates bitArray represented as long number.
	 * Array is used by mutate method, to modify specimen genes. Every bit of genotype for which mask bit is set, is reverted in new specimen's genotype.
	 * 
	 * @return long value representing bit array for mutation.
	 */
	protected long encodeMutation() {
		long result = 0;
		for (int i = 0; i < 64; i++) {
			if (JemoRandom.getRandom().nextDouble() < mutationChance) {
				result += Math.pow(2.0, i);
			}
		}
		return result;
	}

	/**
	 * Returns chance for mutation to happen for a given specimen.
	 * 
	 * @return Double value representing chance of mutation occurence.
	 */
	public double getMutationChance() {
		return mutationChance;
	}

	/**
	 * Stores given number as a new mutation chance.
	 * 
	 * @param mutationChance New mutation chance value.
	 */
	public void setMutationChance(double mutationChance) {
		this.mutationChance = mutationChance;
	}

	/**
	 * Returns true if newly generated specimen is guaranteed to be stronger then its parent.
	 * 
	 * @return Value of improve specimen parameter.
	 */
	public boolean isImproveSpecimen() {
		return improveSpecimen;
	}

	/**
	 * Sets value of improve specimen parameter.
	 * If it is set to true, newly generated specimen will always be stronger, then given specimen.
	 * 
	 * @param improveSpecimen New improve specimen parameter value.
	 */
	public void setImproveSpecimen(boolean improveSpecimen) {
		this.improveSpecimen = improveSpecimen;
	}

	/**
	 * Retrurns currently set domain control policy.
	 * Domain control specifies, how specimen out of domain should be handeled.
	 * 
	 * @return Value of domain control parameter.
	 */
	public DomainControl getDomainControl() {
		return domainControl;
	}

	/**
	 * Sets, how specime out of domain should be handeled by mutation.
	 * 
	 * @param domainControl New domain control policy value.
	 */
	public void setDomainControl(DomainControl domainControl) {
		this.domainControl = domainControl;
	}

	/**
	 * Returns configuration panel being extension of JPanel instance.
	 * Returned panel is displayed by graphical user interface allowing user to confgiure mutation.
	 * This method may return null if no configuration for operator is possible.
	 * 
	 * @return Instance of JPanel with initialized components prepared to configure objects properties.
	 */
	@Override
	public JPanel getConfigPanel() {
		return new ClassicMutationConfigurationPanel(this);
	}
	
	/**
	 * Returns String object containing description of mutation configuration in readable form as a summary.
	 * 
	 * @return String description of object configuration.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Mutation class: " + getClass().getSimpleName() + "\n");
		sb.append("Mutation chance: " + mutationChance + "\n");
		sb.append("Domain control: " + domainControl + "\n");
		sb.append("Improve specimen: " + improveSpecimen + "\n");
		return sb.toString();
	}

}
